package com.example.lib.c2.pc3;

/**
 * 本例 测试 wait 和 notify
 * 容器
 */
class Res {

    private int id;
    /**
     * 标记，默认false
     */
    private boolean flag = false;

    /**
     * 1 使用synchronized锁来处理对共享数据操作产生的安全问题
     * 2 notify 和 wait 需要同步锁包裹，是因为 notify 和 wait 内部都会获取对象锁
     */
    public synchronized void put() {

        //生产者先开始
        if(!flag) {

            id += 1;
            System.out.println(Thread.currentThread().getName() + "生产者 生产了：" + this.id);
            flag = true;

            //唤醒一个wait()（一个冻结的线程），如果没有wait的线程 不会报错， java支持空唤醒
            notify();
            try {
                System.out.println(Thread.currentThread().getName() + "生产者 wait 前：" + this.id);
                //当前线程冻结，释放cpu执行权，当前线程会在这里等着，不会往下执行，直到被其他线程唤醒
                wait();
                System.out.println(Thread.currentThread().getName() + "生产者 wait 后：" + this.id);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void out() {
        // 上面不是说 wait内部会获取锁吗，如果它拿着锁， 这里就不会执行。那么为啥这里执行了
        // 是因为wait后持有的this锁会被释放掉
        if(flag) {
            System.out.println(Thread.currentThread().getName() + "》》》》》》》消费者 消费了：" + this.id);
            id -= 1;
            flag = false;

            notify();

            try {
                System.out.println(Thread.currentThread().getName() + "》》》》》》》消费者 wait 前：" + this.id);
                wait();
                System.out.println(Thread.currentThread().getName() + "》》》》》》》消费者 wait 后：" + this.id);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /*
    使用等待唤醒注意事项：
　　1.使用来wait();冻结，就必须要被其他方notify();，否则一直wait()冻结，所以等待与唤醒是配合一起使用的；
　　2.wait();  notify();  notifyAll(); 等方法必须被synchronized(锁) {包裹起来}，意思就是：wait();  notify();  notifyAll();  必须要有同步锁🔒，否则毫无意义；
 　 3.wait();  notify();  notifyAll(); 等方法必须持有同一把锁🔒，因为：lockA.wait();  只能使用 lockA.notify(); / lockA.notifyAll(); , 它们是使用同一把锁🔒的；
    */

}

class ProduceRunnable implements Runnable {
    private Res res;

    public ProduceRunnable(Res res) {
        this.res = res;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            res.put();
        }
    }
}

class ConsumeRunnable implements Runnable {

    private Res res;

    public ConsumeRunnable(Res res) {
        this.res = res;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            res.out();
        }
    }
}


public class WaitDemo {

    public static void main(String[] args) {
        Res res = new Res();

        ProduceRunnable produceRunnable = new ProduceRunnable(res);
        ConsumeRunnable consumeRunnable = new ConsumeRunnable(res);

        new Thread(produceRunnable).start();
        new Thread(consumeRunnable).start();
    }
}
